package ace.module.account.core.impl.service.impl;

import ace.cmp.core.model.PageDto;
import ace.cmp.core.model.PageResultDto;
import ace.cmp.i18n.api.MessageSourceAssertService;
import ace.cmp.id.generator.api.LongIdGenerator;
import ace.cmp.spring.data.jdbc.querydsl.core.converter.PageConverter;
import ace.cmp.util.AceNumberUtils;
import ace.cmp.util.AceStreamUtils;
import ace.module.account.api.model.dto.IamAccountDetailDto;
import ace.module.account.api.model.dto.IamAccountDto;
import ace.module.account.api.model.dto.IamAccountPassportDto;
import ace.module.account.api.model.dto.IamAccountProfileDto;
import ace.module.account.api.model.input.IamAccountCommonInfoOutInput;
import ace.module.account.api.model.input.IamAccountModifyStatusInput;
import ace.module.account.api.model.input.IamAccountSearchInput;
import ace.module.account.api.model.input.IamAccountSearchOutInput;
import ace.module.account.api.model.input.IamAccountWithProfileSearchInput;
import ace.module.account.api.model.input.RegisterQuickByPhoneInput;
import ace.module.account.api.model.input.RegisterQuickByUsernameInput;
import ace.module.account.api.model.input.RegisterQuickByUsernameOutInput;
import ace.module.account.api.model.input.RegisterQuickByWxMiniAppIdAndOpenIdInput;
import ace.module.account.api.model.input.RegisterQuickCommonInput;
import ace.module.account.api.model.input.account.IamAdminAccountAdminSaveInput;
import ace.module.account.api.model.input.account.IamAdminAccountAdminSearchInput;
import ace.module.account.api.model.input.register.RegisterQuickByPhoneOutInput;
import ace.module.account.core.impl.converter.IamAccountConverter;
import ace.module.account.core.impl.converter.IamAccountProfileConverter;
import ace.module.account.core.impl.converter.RegisterQuickCommonOutInputConverter;
import ace.module.account.core.impl.dao.entity.IamAccount;
import ace.module.account.core.impl.dao.entity.IamApplication;
import ace.module.account.core.impl.dao.repository.IamAccountProfileRepository;
import ace.module.account.core.impl.dao.repository.IamAccountRepository;
import ace.module.account.core.impl.manager.IamAccountManager;
import ace.module.account.core.impl.manager.IamAccountPassportManager;
import ace.module.account.core.impl.manager.IamAccountPasswordManager;
import ace.module.account.core.impl.manager.IamAccountProfileManager;
import ace.module.account.core.impl.manager.IamApplicationConfigManager;
import ace.module.account.core.impl.manager.IamApplicationManager;
import ace.module.account.core.impl.manager.provider.UpmsApplicationProvider;
import ace.module.account.core.impl.model.dto.IamAdminAccountListItemDto;
import ace.module.account.core.impl.service.IamAccountService;
import ace.module.account.core.impl.service.biz.IamAccountBiz;
import ace.module.common.api.model.input.PageInput;
import ace.module.upms.api.UpmsOrganizationApi;
import ace.module.upms.api.UpmsRoleApi;
import ace.module.upms.api.model.dto.UpmsOrganizationDto;
import ace.module.upms.api.model.dto.UpmsRoleDto;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author caspar
 * @date 2023/3/18 16:16
 */
@Component
@AllArgsConstructor
public class IamAccountServiceImpl implements IamAccountService {

  private final IamAccountRepository iamAccountRepository;
  private final IamAccountProfileRepository iamAccountProfileRepository;
  private final IamAccountManager iamAccountManager;
  private final IamAccountPasswordManager iamAccountPasswordManager;
  private final IamApplicationManager iamApplicationManager;
  private final IamAccountPassportManager iamAccountPassportManager;
  private final IamAccountProfileManager iamAccountProfileManager;
  private final MessageSourceAssertService assertService;
  private final IamApplicationConfigManager iamApplicationConfigManager;
  private final LongIdGenerator idGenerator;
  private final PageConverter pageConverter;
  private final IamAccountConverter iamAccountConverter;
  private final IamAccountProfileConverter iamAccountProfileConverter;
  private final RegisterQuickCommonOutInputConverter registerQuickCommonOutInputConverter;
  private final UpmsRoleApi upmsRoleApi;
  private final UpmsOrganizationApi upmsOrganizationApi;
  private final UpmsApplicationProvider upmsApplicationProvider;
  private final IamAccountBiz iamAccountBiz;

  @Transactional(rollbackFor = Exception.class)
  @Override
  public Long registerQuickByPhone(RegisterQuickByPhoneInput input) {
    return iamAccountBiz.registerQuickByPhone(input);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public Long registerQuickByWxMiniAppIdAndOpenId(RegisterQuickByWxMiniAppIdAndOpenIdInput input) {
    return this.iamAccountBiz.registerQuickByWxMiniAppIdAndOpenId(input);
  }

  @Override
  public Page<IamAccountDetailDto> pageWithProfile(String query, Long iamAppId, Pageable pageable) {

    Page<IamAccountDto> pageResult = this.iamAccountManager.pageWithProfile(query, iamAppId,
        pageable);

    return pageConverter.toPageMapperList(pageResult, pageable,
        this::toIamAccountDetailDto);
  }

  @Override
  public PageResultDto<IamAccountDetailDto> pageWithProfile(
      IamAccountWithProfileSearchInput input, PageDto page) {

    Pageable pageable = pageConverter.toQPageRequest(page);

    Page<IamAccountDto> pageResult = iamAccountManager.pageWithProfile(input, pageable);

    return pageConverter.toMapperList(pageResult,
        this::toIamAccountDetailDto);
  }

  @Override
  public PageResultDto<IamAccountDetailDto> searchWithProfile(
      PageInput<IamAccountSearchInput> input) {

    Pageable pageable = pageConverter.toQPageRequest(input.getPage());

    Page<IamAccountDto> pageResult = iamAccountManager.search(input.getData(), pageable);

    return pageConverter.toMapperList(pageResult, this::toIamAccountDetailDto);
  }

  private List<IamAccountDetailDto> toIamAccountDetailDto(List<IamAccountDto> iamAccountDtoList) {

    List<Long> iamAccountIdList = iamAccountDtoList
        .stream()
        .map(IamAccountDto::getId)
        .collect(Collectors.toList());

    Map<Long, String> accountIdAndPhoneMap = iamAccountPassportManager.getPhonePassportMapByAccountId(iamAccountIdList);

    Map<Long, IamAccountProfileDto> accountIdAndIamAccountProfileDtoMap = iamAccountProfileManager
        .getAccountIdAndProfileDtoMapByAccountId(iamAccountIdList);

    return AceStreamUtils.ofNullable(iamAccountDtoList)
        .map(iamAccountDto -> {
          return IamAccountDetailDto.builder()
              .account(iamAccountDto)
              .profile(accountIdAndIamAccountProfileDtoMap.get(iamAccountDto.getId()))
              .phone(accountIdAndPhoneMap.get(iamAccountDto.getId()))
              .build();

        })
        .collect(Collectors.toList());
  }

  @Override
  public PageResultDto<IamAccountDetailDto> searchWithProfileByOutInfo(
      PageInput<IamAccountSearchOutInput> input) {

    IamApplication iamApplication =
        this.iamApplicationManager.getByOutTypeAndOutId(
            input.getData().getAppOutInfo().getOutType(),
            input.getData().getAppOutInfo().getOutId());

    if (iamApplication == null) {
      return new PageResultDto<>();
    }

    PageInput<IamAccountSearchInput> searchInput =
        PageInput
            .<IamAccountSearchInput>builder()
            .page(input.getPage())
            .data(IamAccountSearchInput.builder()
                .filter(input.getData().getFilter())
                .iamAppId(iamApplication.getId())
                .build())
            .build();

    return this.searchWithProfile(searchInput);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public Long registerQuickByPhoneOutInput(RegisterQuickByPhoneOutInput input) {
    IamApplication iamApplication = this.iamApplicationManager.getAndCheckByOutTypeAndOutId(
        input.getOutInfo().getOutType(), input.getOutInfo().getOutId());

    RegisterQuickCommonInput commonInput = registerQuickCommonOutInputConverter.convert(
        input.getBase().getCommon()
    );

    commonInput.setIamAppId(iamApplication.getId());

    String phone = input.getBase().getPhone();

    RegisterQuickByPhoneInput registerQuickByPhoneInput = RegisterQuickByPhoneInput.builder()
        .commonInput(commonInput)
        .phone(phone)
        .build();

    return this.registerQuickByPhone(registerQuickByPhoneInput);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public void modifyCommonInfo(IamAccountCommonInfoOutInput input) {
    Long accountId = input.getAccountId();

    IamAccount iamAccount = this.iamAccountManager
        .getAndCheckNotNullByIdAndDeleteFlagIsFalse(accountId);

    iamAccount.setStatus(iamAccount.getStatus());
    iamAccount.setPasswordChangeRequired(iamAccount.getPasswordChangeRequired());

    this.iamAccountProfileManager.insertOrUpdate(
        input.getAccountId(),
        iamAccount.getIamAppId(),
        input.getCommon()
    );

    this.iamAccountRepository.updateById(iamAccount, false);
  }

  @Override
  public void modifyStatus(IamAccountModifyStatusInput input) {
    this.iamAccountManager.modifyStatus(Arrays.asList(input));
  }

  @Override
  public void modifyStatus(List<IamAccountModifyStatusInput> input) {
    this.iamAccountManager.modifyStatus(input);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public Long registerQuickByUsername(RegisterQuickByUsernameInput input) {
    return this.iamAccountBiz.registerQuickByUsername(input);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public Long registerQuickByUsernameAndAppOutInfo(RegisterQuickByUsernameOutInput input) {
    IamApplication iamApplication =
        iamApplicationManager.getAndCheckByOutTypeAndOutId(
            input.getOutInfo().getOutType(), input.getOutInfo().getOutId());

    RegisterQuickCommonInput commonInput = RegisterQuickCommonInput.builder().build();
    BeanUtils.copyProperties(input.getCommon(), commonInput);
    commonInput.setIamAppId(iamApplication.getId());

    RegisterQuickByUsernameInput usernameInput =
        RegisterQuickByUsernameInput.builder()
            .username(input.getUsername())
            .commonInput(commonInput)
            .build();

    return this.registerQuickByUsername(usernameInput);
  }

  @Override
  public Page<IamAdminAccountListItemDto> adminSearch(IamAdminAccountAdminSearchInput input, Pageable pageable) {
    IamApplication adminIamApp = this.iamApplicationManager.getAdminIamApplication();
    Page<IamAccount> iamAccountPage = this.iamAccountManager.adminSearch(adminIamApp.getId(), input, pageable);
    return this.assembleIamAccountPageToAdminAccountListItemDto(iamAccountPage);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public Long adminSave(IamAdminAccountAdminSaveInput input) {
    boolean isAdd = AceNumberUtils.isNullOrZero(input.getId());
    Long iamAccountId;
    if (isAdd) {
      iamAccountId = this.iamAccountBiz.adminAdd(input);
    } else {
      iamAccountId = this.iamAccountBiz.adminModify(input);
    }
    return iamAccountId;
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public void remove(List<Long> ids) {
    this.iamAccountManager.remove(ids);
  }


  private Page<IamAdminAccountListItemDto> assembleIamAccountPageToAdminAccountListItemDto(Page<IamAccount> iamAccountPage) {
    Long upmsAppId = this.upmsApplicationProvider.getAndCheckNotNullAdminApplication().getId();

    List<Long> iamAccountIds = iamAccountPage.get()
        .map(IamAccount::getId)
        .collect(Collectors.toList());
    Map<Long, IamAccountProfileDto> accountIdAndProfileMap = this.iamAccountProfileManager.getAccountIdAndProfileDtoMapByAccountId(iamAccountIds);
    Map<Long, IamAccountPassportDto> accountIdAndUsernamePassportMap = this.iamAccountPassportManager.getAccountIdAndUsernamePassportDtoMapByAccountId(iamAccountIds);
    Map<Long, IamAccountPassportDto> accountIdAndPhonePassportMap = this.iamAccountPassportManager.getAccountIdAndPhonePassportDtoMapByAccountId(iamAccountIds);
    Map<Long, List<UpmsRoleDto>> accountIdAndRoleMap = this.upmsRoleApi.getAccountIdAndRoleMapByUpmsAppIdAndIamAccountIdIn(upmsAppId, iamAccountIds).check();
    Map<Long, List<UpmsOrganizationDto>> accountIdAndOrgMap = this.upmsOrganizationApi.getAccountIdAndOrgMapByUpmsAppIdAndIamAccountIdIn(upmsAppId, iamAccountIds).check();

    return iamAccountPage.map(iamAccount -> this.assembleIamAccountToAdminAccountListItemDto(iamAccount,
        accountIdAndProfileMap,
        accountIdAndUsernamePassportMap,
        accountIdAndPhonePassportMap,
        accountIdAndRoleMap,
        accountIdAndOrgMap)
    );
  }

  private IamAdminAccountListItemDto assembleIamAccountToAdminAccountListItemDto(IamAccount iamAccount,
      Map<Long, IamAccountProfileDto> accountIdAndProfileMap,
      Map<Long, IamAccountPassportDto> accountIdAndUsernamePassportMap,
      Map<Long, IamAccountPassportDto> accountIdAndPhonePassportMap,
      Map<Long, List<UpmsRoleDto>> accountIdAndRoleMap,
      Map<Long, List<UpmsOrganizationDto>> accountIdAndOrgMap
  ) {
    if (Objects.isNull(iamAccount)) {
      return null;
    }
    IamAccountDto iamAccountDto = this.iamAccountManager.convertToEntityDto(iamAccount);

    Long accountId = iamAccountDto.getId();

    String accountPhone = Optional.ofNullable(accountIdAndPhonePassportMap.get(accountId))
        .map(IamAccountPassportDto::getPassport)
        .orElse(StringUtils.EMPTY);

    String username = Optional.ofNullable(accountIdAndUsernamePassportMap.get(accountId))
        .map(IamAccountPassportDto::getPassport)
        .orElse(StringUtils.EMPTY);

    IamAccountProfileDto profileDto = accountIdAndProfileMap.get(accountId);
    List<UpmsRoleDto> roles = accountIdAndRoleMap.get(accountId);
    List<UpmsOrganizationDto> organizations = accountIdAndOrgMap.get(accountId);

    return IamAdminAccountListItemDto.builder()
        .account(iamAccountDto)
        .profile(profileDto)
        .accountPhone(accountPhone)
        .username(username)
        .roles(roles)
        .organizations(organizations)

        .build();
  }
}
